import { join } from "path";
import Config from "../core/Config";
import { Extensions, FixEngineConfig } from "../core/Defines";
import { Environment } from "../core/Environment";
import { CommonUtils } from "../core/CommonUtils";
import { FileUtils } from "../core/FileUtils";
import { minimatch } from "minimatch";
import { existsSync, readFileSync, writeFileSync } from "fs";

export default class Helper extends Config<FixEngineConfig> {
    module: string = "【FixEngineHelper】";

    readonly defaultData: FixEngineConfig = {
        "include": [
            "**/libcocos2d.vcxproj.filters",
            "**/assets-manager/AssetsManagerEx.*",
            "**/assets-manager/Manifest.*",
            "**/jsb_cocos2dx_extension_auto.*",
            "**/HelloJavascript.vcxproj",
            "**/app/build.gradle",
            "utils/api/**",
            "**/js-template-default/frameworks/runtime-src/Classes/AppDelegate.cpp",
            "**/js-template-link/frameworks/runtime-src/Classes/AppDelegate.cpp",
        ],
        exclude: [],
    }

    get path() {
        return join(this.configPath, "fix_engine.json");
    }

    /**@description 获取当前 Creator 版本 */
    get creatorVersion() {
        return Environment.creatorVersion;
    }

    /**@description 本地引擎路径 */
    protected get customEngineRoot() {
        return join(this.projPath, `engine/${this.creatorVersion}`);
    }

    /**@description 备份指定版本引擎路径 */
    protected get backupEngineLocalPath() {
        return join(this.customEngineRoot, `backupEngine`);
    }

    /**@description 修改指定版本引擎路径 */
    protected get customEnginePath() {
        return join(this.customEngineRoot, `customEngine`);
    }

    /**@description creator安装路径 */
    protected get creatorPath() {
        return Environment.creatorPath;
    }

    private _curExtensionPath: string = null!;
    get curExtensionPath() {
        if (!this._curExtensionPath) {
            this._curExtensionPath = join(this.extensionsPath, Extensions.FixEngine);
        }
        return this._curExtensionPath;
    }

    read(isReload: boolean = false): void {
        super.read(isReload);
        if (this.data) {
            this.data.include = this.data.include.concat(this.defaultData.include);
            this.data.include = CommonUtils.uniqueArray(this.data.include);
            this.data.exclude = this.data.exclude.concat(this.defaultData.exclude);
            this.data.exclude = CommonUtils.uniqueArray(this.data.exclude);
        }
    }

    /**@description 计算path下所有文件的md5 */
    protected async md5engine(path: string): Promise<{ [key: string]: string } | null> {
        const files = FileUtils.getFiles(path, (info) => {
            const relative = info.relative.replace(/\\/g, "/");

            let isInclude = false;
            // 检查是否在 include 列表中
            for (let i = 0; i < this.data!.include.length; i++) {
                const v = this.data!.include[i];
                if (minimatch(relative, v, { dot: true, matchBase: true })) {
                    isInclude = true;
                    break;
                }
            }

            // 检查是否在 exclude 列表中
            for (let i = 0; i < this.data!.exclude.length; i++) {
                const v = this.data!.exclude[i];
                if (minimatch(relative, v, { dot: true, matchBase: true })) {
                    isInclude = false;
                    break;
                }
            }
            return isInclude;
        }, path);

        // 限制并发处理的数量
        const concurrentLimit = 50;
        let assets: { [key: string]: string } = {};
        // 批量处理文件，计算 MD5
        for (let i = 0; i < files.length; i += concurrentLimit) {
            const batch = files.slice(i, i + concurrentLimit);

            try {
                // 批量处理文件，计算 MD5
                const results = await Promise.all(batch.map(async (file) => {
                    try {
                        const md5 = await FileUtils.md5(file.path);
                        return {
                            relative: FileUtils.formatPath(file.relative),
                            size: file.size,
                            md5: md5,
                        }
                    } catch (error) {
                        this.logger.error(`计算文件 ${file.path} MD5失败: ${error}`);
                        return null;

                    }
                }));

                // 更新 assets 对象
                results.forEach(result => {
                    if (result) {
                        assets[result.relative] = result.md5;
                    }
                });
            } catch (error) {
                this.logger.error(`批量处理文件失败: ${error}`);

            }
        }

        return assets;
    }

    protected async getAllFiles(dir: string): Promise<{ [key: string]: string } | null> {
        this.read();
        const files = await this.md5engine(dir);
        // writeFileSync(join(__dirname, "files.json"), JSON.stringify(files, null, 4), "utf-8");
        return files;
    }

    protected saveMd5(files: { [key: string]: string } | null, isRaw: boolean = true) {
        FileUtils.createDir(this.customEnginePath);
        const path = join(this.customEnginePath, `${isRaw ? "raw_" : "local_"}files.json`);
        writeFileSync(path, JSON.stringify(files, null, 4), "utf-8");
    }

    protected readMd5(isRaw: boolean = true): { [key: string]: string } | null {
        const path = join(this.customEnginePath, `${isRaw ? "raw_" : "local_"}files.json`);
        if (existsSync(path)) {
            return JSON.parse(readFileSync(path, "utf-8"));
        }
        return null;
    }

    /**@description 备份引擎 */
    async backupEngine() {
        try {
            this.logger.log(`${this.module}开始备份引擎...`);
            const files: { [key: string]: string } | null = await this.getAllFiles(this.creatorPath);
            for (const key in files) {
                files[key] = files[key].replace(/\\/g, "/"); // 替换路径中的反斜杠为正斜杠
                const sourcePath = join(this.creatorPath, key);
                const destPath = join(this.backupEngineLocalPath, key);
                this.logger.log(`${this.module} 备份文件: ${sourcePath} -> ${destPath}`);
                await FileUtils.copyFile(sourcePath, destPath, true);
            }

            // 保存引擎原始文件的 MD5 放到raw_files.json
            this.saveMd5(files);
            // 保存引擎本地文件的 MD5 放到local_files.json
            const customFiles = await this.getAllFiles(this.customEnginePath);
            this.saveMd5(customFiles, false);
            this.logger.log(`${this.module}备份引擎完成，文件数量: ${Object.keys(files || {}).length}`);

        } catch (error) {
            this.logger.error(`${this.module}备份引擎失败: ${error}`);
        }
    }

    /**@description 恢复引擎 */
    async restoreEngine() {
        try {
            this.logger.log(`${this.module}开始恢复引擎...`);
            if (!existsSync(this.backupEngineLocalPath)) {
                throw new Error(`${this.module}备份引擎路径不存在: ${this.backupEngineLocalPath}`);
            }
            const files: { [key: string]: string } | null = await this.getAllFiles(this.backupEngineLocalPath);
            if (!files || Object.keys(files).length === 0) {
                throw new Error(`${this.module}备份引擎文件为空,没有可以还原的文件: ${this.backupEngineLocalPath}`);
            }

            let rawMd5 = this.readMd5(true);
            if (!rawMd5) {
                throw new Error(`${this.module}备份引擎原始MD5文件不存在!请先备份引擎`);
            }

            for (const key in files) {
                files[key] = files[key].replace(/\\/g, "/"); // 替换路径中的反斜杠为正斜杠
                const sourcePath = join(this.backupEngineLocalPath, key);
                const destPath = join(this.creatorPath, key);
                this.logger.log(`${this.module} 恢复文件: ${sourcePath} -> ${destPath}`);
                await FileUtils.copyFile(sourcePath, destPath, true);
            }

            const customMd5 = this.readMd5(false);
            for (const key in customMd5) {
                if (!files[key]) {
                    const filePath = join(this.creatorPath, key);
                    let isDel = FileUtils.delFile(filePath);
                    if (isDel) {
                        this.logger.log(`${this.module} 删除自定义引擎文件: ${filePath}`);
                    }
                }
            }

            this.logger.log(`${this.module}恢复引擎完成，文件数量: ${Object.keys(files || {}).length}`);

        } catch (error) {
            this.logger.error(`${this.module}恢复引擎失败: ${error}`);

        }
    }

    async syncEngine(fromMd5: { [key: string]: string } | null,
        toMd5: { [key: string]: string } | null,
        fromPath: string,
        toPath: string,
        tag: string
    ) {
        let addFiles: { [key: string]: string } = {};
        let delFiles: { [key: string]: string } = {};
        let changeFiles: { [key: string]: string } = {};

        for (const key in fromMd5) {
            if (!toMd5 || !toMd5[key]) {
                addFiles[key] = fromMd5[key];
            } else if (fromMd5[key] !== toMd5[key]) {
                changeFiles[key] = fromMd5[key];
            }
        }

        for (const key in toMd5) {
            if (!fromMd5 || !fromMd5[key]) {
                delFiles[key] = toMd5[key];
            }
        }

        for (const key in addFiles) {
            const sourcePath = join(fromPath, key);
            const destPath = join(toPath, key);
            this.logger.log(`${this.module} ${tag} 添加文件: ${sourcePath} -> ${destPath}`);
            await FileUtils.copyFile(sourcePath, destPath, true);
        }

        for (const key in delFiles) {
            const filePath = join(toPath, key);
            let isDel = FileUtils.delFile(filePath);
            if (isDel) {
                this.logger.log(`${this.module} ${tag} 删除文件: ${filePath}`);
            }
        }

        for (const key in changeFiles) {
            const sourcePath = join(fromPath, key);
            const destPath = join(toPath, key);
            this.logger.log(`${this.module} ${tag} 修改文件: ${sourcePath} -> ${destPath}`);
            await FileUtils.copyFile(sourcePath, destPath, true);
        }
    }

    async syncEngine2CustomEngine() {
        // 为什么要同步引擎到自定义引擎？自定义引擎为啥会修改掉？
        try {
            this.logger.log(`${this.module}开始同步引擎到自定义引擎...`);

            const rawMd5 = this.readMd5(true);
            if (!rawMd5) {
                throw new Error(`${this.module}备份引擎MD5文件不存在!请先备份引擎`);
            }
            const curRawEngineMd5 = await this.getAllFiles(this.creatorPath);
            if (Object.keys(curRawEngineMd5 || {}).length === 0) {
                throw new Error(`${this.module}当前引擎文件为空,没有可以同步的文件: ${this.creatorPath}`);
            }
            const customEngineMd5 = await this.getAllFiles(this.customEnginePath);
            await this.syncEngine(curRawEngineMd5, customEngineMd5,
                this.creatorPath, this.customEnginePath, "【引擎->自定义引擎】");
            this.saveMd5(curRawEngineMd5, true);
            this.logger.log(`${this.module}同步引擎到自定义引擎完成`);
        } catch (error) {
            this.logger.error(`${this.module}同步引擎到自定义引擎失败: ${error}`);
        }
    }

    async syncCustomEngine2Engine() {
        try {
            const rawEngineMd5 = this.readMd5(true);
            if (!rawEngineMd5) {
                throw new Error(`${this.module}备份引擎MD5文件不存在!请先备份引擎`);
            }

            const curCustomEngineMd5 = await this.getAllFiles(this.customEnginePath);

            let curRawEngineMd5 = await this.getAllFiles(this.creatorPath);
            await this.syncEngine(curCustomEngineMd5, curRawEngineMd5,
                this.customEnginePath, this.creatorPath, "【自定义引擎->引擎】");

            this.saveMd5(curCustomEngineMd5, false);
            this.logger.log(`${this.module}同步自定义引擎到引擎完成`);
        } catch (error) {
            this.logger.error(`${this.module}同步自定义引擎到引擎失败: ${error}`);
        }
    }

    checkBackupEngine() {
        try {
            if (!existsSync(this.backupEngineLocalPath)) {
                return false;
            }
            return true;
        } catch (error) {
            return false;
        }
    }

    async run() {
        await this.restoreEngine();
        await this.backupEngine();
        await this.syncCustomEngine2Engine();
    }
}